#include "ContentEncoding.h"
#include "zlib.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define DECOMP_SIZE 0x10000             /* buffer size for decompressed data */

#define GZIP_MAGIC_0 0x1f
#define GZIP_MAGIC_1 0x8b

/* gzip flag byte */
#define ASCII_FLAG   0x01 /* bit 0 set: file probably ascii text */
#define HEAD_CRC     0x02 /* bit 1 set: header CRC present */
#define EXTRA_FIELD  0x04 /* bit 2 set: extra field present */
#define ORIG_NAME    0x08 /* bit 3 set: original file name present */
#define COMMENT      0x10 /* bit 4 set: file comment present */
#define RESERVED     0xE0 /* bits 5..7: reserved */

static
std::string InflateStream(z_stream *z)
{
	std::string outstr;

	uInt nread = z->avail_in;
	Bytef *orig_in = z->next_in;
	int status;                   /* zlib status */
	int allow_restart = 1;

	char *decomp = new char[DECOMP_SIZE];
	/* because the buffer size is fixed, iteratively decompress 
	and append to the output buffer. */
	for (;;) {
		/* (re)set buffer for decompressed output for every iteration */
		z->next_out = (Bytef *)decomp;
		z->avail_out = DECOMP_SIZE;

		status = inflate(z, Z_SYNC_FLUSH);
		if(status == Z_OK || status == Z_STREAM_END) {
			allow_restart = 0;
			if(DECOMP_SIZE - z->avail_out) {
				outstr.append(decomp, DECOMP_SIZE - z->avail_out);
			}

			/* Done? clean up, return */
			if(status == Z_STREAM_END) {
				goto INFLATE_DONE;
			}

			/* Done with these bytes, exit */
			if(status == Z_OK && z->avail_in == 0) {
				goto INFLATE_DONE;
			}
		} else if(allow_restart && status == Z_DATA_ERROR) {
			/* some servers seem to not generate zlib headers, 
			so this is an attempt to fix and continue anyway */

			(void) inflateEnd(z);	/* don't care about the return code */
			if(inflateInit2(z, -MAX_WBITS) != Z_OK) {
				goto INFLATE_DONE;
			}
			z->next_in = orig_in;
			z->avail_in = nread;
			allow_restart = 0;
			continue;
		} else {   /* Error; exit loop, handle below */
			goto INFLATE_DONE;
		}
	}

INFLATE_DONE:
	inflateEnd(z);
	delete[] decomp;

	return  outstr;
}

std::string UnencodeDeflateData(const char *datap, size_t size)
{

	z_stream z;               /* zlib state structure */
	memset(&z, 0, sizeof(z));
	if(inflateInit(&z) != Z_OK)
		return std::string();

	z.next_in = (Bytef *) datap;
	z.avail_in = (uInt) size;

	return InflateStream(&z);
}

std::string UnencodeGzipData(const char *datap, size_t size)
{
	z_stream z;
	memset(&z, 0, sizeof(z));
	/* zlib ver. >= 1.2.0.4 supports transparent gzip decompressing */
	if(inflateInit2(&z, MAX_WBITS+32) != Z_OK) 
		return std::string();
	// else { ... } /* we must parse the gzip header ourselves */
	
	/* Let zlib handle the gzip decompression entirely */
	z.next_in = (Bytef *) datap;
	z.avail_in = (uInt) size;
	/* Now uncompress the data */
	return InflateStream(&z);
}